/* eslint-disable no-restricted-syntax */
/* eslint-disable no-bitwise */
const fs = require('fs-extra');
const path = require('path');
const moment = require('moment');
const mongoose = require('mongoose');

const files = require('./../../../files');
const ParentReporter = require('./../default-report');
const { collectCoverageKind, versionType } = require('../../../project-versions');
const Docx = require('./../adapter/docx');
const ProjectVersionSpliter = require('./../../project-version-spliter');
const DataAdapter = require('./data-adapter');
const invokeRelations = require('./../../../invoke-relations');
const utility = require('../../../utility');
const functions = require('./../../../functions');
const tests = require('./../../../tests');
const classCreator = require('./../base-report-test-case-transformer');
const logger = require('../../../logger');
const UserDefinedFields = require('../../../mongo/user-defined-fields');
const Function = require('../../../mongo/function');
const HtmlClass = require('./html');

class TonghaoReporter extends ParentReporter {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null, format, complete) {
    super(versionId, locale, version, project, creator, outputDir, format, complete);
    this.setDataAdapter(new DataAdapter());
    this.functionAnnotationsMap = new Map();
    if (format === 'html' || format === 'pdf') {
      this.html = new HtmlClass(versionId, locale, version, project, creator, this.outputDir, format, complete);
    }
  }

  /**
   * 按文件分割
   * @return {Promise}
   */
  async spliteByFile(callback = null, progressIndicator) {
    if (this.html) {
      return this.html.spliteByFile(callback, progressIndicator);
    }
    // 集成测试创建函数注释map
    if (this.isIntegrationTest()) {
      const functionAnnotationField = await UserDefinedFields.findOne({
        domain: 'integrationTest',
        scope: 'function',
        name: '函数注释',
      });
      if (!functionAnnotationField) {
        return super.spliteByFile(callback, progressIndicator);
      }
      const functionAnnotations = await Function.find({
        userDefinedFields: { $elemMatch: { key: functionAnnotationField._id } },
        versionId: mongoose.Types.ObjectId(this.versionId),
      }, { _id: 1, userDefinedFields: 1 });
      functionAnnotations.forEach((func) => {
        this.functionAnnotationsMap.set(
          func._id.toString(),
          func.userDefinedFields.find((field) => field.key.toString() === functionAnnotationField._id.toString()).value
        );
      });
    }
    return super.spliteByFile(callback, progressIndicator);
  }

  arrangeRelations(funcRelations, func) {
    const funcMap = new Map();
    funcRelations.forEach((r) => {
      const key = r.fromFuncId.toString();
      if (!funcMap.get(key)) {
        funcMap.set(key, {
          functionId: r.fromFuncId,
          children: new Map(),
        });
      }
      const funcFrom = funcMap.get(key);
      if (r.functionId.toString() === funcFrom.functionId.toString()) {
        // 入口函数
        funcFrom.functionName = func.functionName;
      }

      if (r.toFuncId) {
        // 已定义
        if (!funcMap.get(r.toFuncId.toString())) {
          funcMap.set(r.toFuncId.toString(), {
            functionId: r.toFuncId,
            children: new Map(),
          });
        }
        const funcTo = funcMap.get(r.toFuncId.toString());
        funcTo.functionName = r.toFuncName;
        funcFrom.children.set(r.toFuncName, {
          toFunc: funcTo,
        });
      } else {
        // 未定义
        funcFrom.children.set(r.toFuncName, {
          toFunc: {
            functionName: r.toFuncName,
            mangledId: r.toFuncMangledId,
          },
        });
      }
    });
    return funcMap;
  }

  /**
 * 生成测试报告总览
 * @returns {Promise}
 */
  generateSummary() {
    if (this.html) {
      return this.html.generateSummary();
    }
    const summary = {
      reportTime: moment().format('YYYY/MM/DD HH:mm:ss'),
      isIntegrationTest: this.isIntegrationTest(),
      reportName: this.getReportName(),
      coverageRange: !this.version.collectCoverageKind ? collectCoverageKind.all : this.version.collectCoverageKind,
      jumpable: this.jumpable,
      isRegressionTest: this.isRegressionTest(),
    };
    return this.handleSummary(summary) // 注意回归版本（只显示added modified函数）summary 中的文件覆盖率并不是正确的
      .then(async () => {
        if (!this.isIntegrationTest()) {
          return Promise.resolve();
        }
        summary.invokeLines = [];
        const relations = await utility.arrayChunkOperation(summary.functionsOverview, 10,
          async (func) => invokeRelations.retrieveRelations(func._id, {
            initRelations: true,
            completeDepth: true,
          }));
          // 过滤掉4层以上及被打桩的函数
        summary.functionsOverview.forEach((func, index) => {
          const funcRelations = relations[index].filter((r) => r.depth.minDepth < 5 && !r.enableStub);
          const funcMap = this.arrangeRelations(funcRelations, func);
          const invokeLines = [];
          const invokeStack = [];
          const invokeIndex = [];
          invokeStack.push({
            id: func._id.toString(),
            name: func.functionName,
          });
          invokeIndex.push(invokeStack.length - 1);
          while (invokeIndex.length > 0) {
            const currentIndex = invokeIndex[invokeIndex.length - 1];
            // 当前节点有子节点 且调用层数（调用指针队列长度）小于4 且未发生循环调用
            if (invokeStack[currentIndex].id
              && funcMap.get(invokeStack[currentIndex].id) // 无调用关系取不到
              && funcMap.get(invokeStack[currentIndex].id).children.size > 0
              && invokeIndex.length < 4
              && invokeIndex.map((nodeIndex) => invokeStack[nodeIndex])
                .filter((funcData) => funcData.id === invokeStack[currentIndex].id).length < 2) {
              // 当前节点子节点入栈
              const tempStack = [];
              funcMap.get(invokeStack[currentIndex].id).children.forEach((funcData) => {
                tempStack.push({
                  id: funcData.toFunc.functionId ? funcData.toFunc.functionId.toString() : undefined,
                  name: funcData.toFunc.functionName,
                });
              });
              tempStack.reverse(); // 调用函数的顺序
              invokeStack.push(...tempStack);
              invokeIndex.push(invokeStack.length - 1);
            } else {
              // 获取路径
              invokeLines.push(
                invokeIndex.map((nodeIndex) => invokeStack[nodeIndex]).map((funcData) => funcData.name)
              );
              if (currentIndex === 0) {
                break;
              }
              invokeIndex[invokeIndex.length - 1] -= 1;
              while (invokeIndex[invokeIndex.length - 1] !== 0 && invokeIndex[invokeIndex.length - 1] === invokeIndex[invokeIndex.length - 2]) {
                invokeIndex.pop();
                invokeIndex[invokeIndex.length - 1] -= 1;
              }
              invokeStack.splice(invokeIndex[invokeIndex.length - 1] + 1);
              if (invokeIndex[invokeIndex.length - 1] === 0) {
                break;
              }
            }
          }
          invokeLines.forEach((line) => {
            while (line.length < 4) {
              line.push('N/A');
            }
          });
          summary.invokeLines = summary.invokeLines.concat(
            invokeLines.map((line) => ({
              level0: line[0],
              level1: line[1],
              level2: line[2],
              level3: line[3],
            }))
          );
        });
        summary.invokeLines.forEach((line, index) => {
          line.lineIndex = index + 1;
        });
        return Promise.resolve();
      })
      .then(() => {
        const summaryReportPath = path.join(
          this.outputDir,
          this.project.projectName,
          this.version.versionName,
          `${this.project.projectName}项目软件${this.isIntegrationTest() ? '集成' : '单元'}测试报告.docx`
        );
        const standardReportPath = path.join(
          this.outputDir,
          this.project.projectName,
          this.version.versionName,
          `${this.project.projectName}项目软件${this.isIntegrationTest() ? '集成' : '单元'}测试规范.docx`
        );
        const summaryTemplateName = this.isIntegrationTest() ? 'tonghao-it-summary' : 'tonghao-summary';
        const standardTemplateName = this.isIntegrationTest() ? 'tonghao-it-summary-standard' : 'tonghao-summary-standard';
        return fs.ensureDir(path.dirname(summaryReportPath))
          .then(() => this.generateReport(
            summaryTemplateName, summary, summaryReportPath
          ))
          .then(() => fs.ensureDir(path.dirname(standardReportPath)))
          .then(() => this.generateReport(
            standardTemplateName, summary, standardReportPath
          ));
      });
  }

  generateReport(template, data, output) {
    let templateEngine = null;
    return (async () => {
      this.templateEngine.set(template, new Docx(path.join(__dirname, 'template', 'docx', `${template}.docx`)));
      templateEngine = this.templateEngine.get(template);
      await templateEngine.generate(
        data,
        output,
        true
      );
      return output;
    })();
  }

  handleFile(file, versionDir) {
    return new Promise((resolve, reject) => {
      ProjectVersionSpliter.prototype.handleFile.call(this, file, versionDir)
        .then(async (res) => {
          const { typeSystem, functions, resultCount } = res;
          if (!resultCount) {
            return null;
          }
          const positiveFunctions = functions.filter((func) => {
            if (!func.tests.length) {
              return false;
            }
            if (this.isRegressionTest()) {
              if (!func.changed) {
                return false;
              }
            }
            return true;
          });
          if (this.isIntegrationTest()) {
            const relations = await utility.arrayChunkOperation(positiveFunctions, 10,
              async (func) => invokeRelations.retrieveRelations(func._id, {
                initRelations: true,
                completeDepth: true,
              }));
            positiveFunctions.forEach((func, index) => {
              func.relations = relations[index];
            });
          }
          file.dirName = (await files.getFile(file.directoryId)).fileName;
          return this.generateFileDocxReport(file, typeSystem, positiveFunctions);
        })
        .then((data) => {
          // 回归测试无新增或修改函数
          if (!data || data.functions.length === 0) {
            return Promise.resolve();
          }
          const fileReportPath = path.join(
            this.outputDir,
            this.project.projectName,
            this.version.versionName,
            this.project.projectName,
            `${this.project.projectName}项目${file.fileName}模块${this.isIntegrationTest() ? '集成' : '单元'}测试用例集.docx`
          );
          const fileTemplateName = this.isIntegrationTest() ? 'tonghao-it-file' : 'tonghao-file';
          return fs.ensureDir(path.dirname(fileReportPath))
            .then(() => this.generateReport(
              fileTemplateName, data, fileReportPath
            ));
        })
        .then((reportPath) => {
          resolve({ reportPath });
        })
        .catch(reject);
    });
  }

  /**
   * 获取函数列表详细信息
   * @param {Object} typeSystem 类型系统
   * @param {Array} functions 函数列表
   * @returns {Array}
   */
  getFunctionDetailsOfFile(typeSystem, functions) {
    const resFunctions = [];
    functions.sort((a, b) => {
      if (a.index < b.index) return -1;
      if (a.index > b.index) return 1;
      return 0;
    });
    functions.forEach((func) => {
      let annotations;
      func.udfs.forEach((udf) => {
        if (udf.name === '函数注释') {
          annotations = udf.value;
        }
      });
      resFunctions.push({
        functionName: func.functionName,
        description: func.description,
        // summary: this.getSummaryOfFunction(func),
        testCount: func.tests.length,
        tests: this.getTestDetailsOfFunction(typeSystem, func),
        annotations,
      });
    });
    resFunctions.forEach((func, index) => {
      func.index = index + 1;
    });
    return resFunctions;
  }

  /**
   * 获取用例列表详细信息
   * @param {Object} typeSystem 类型系统
   * @param {Object} func 函数信息
   */
  getTestDetailsOfFunction(typeSystem, func) {
    const resTests = [];
    func.tests.sort((a, b) => {
      const c1Time = a.group ? mongoose.Types.ObjectId(a.weight.substr(0, a.weight.length - 4)).getTimestamp().getTime()
        : a._id.getTimestamp().getTime();
      const c2Time = b.group ? mongoose.Types.ObjectId(b.weight.substr(0, b.weight.length - 4)).getTimestamp().getTime()
        : b._id.getTimestamp().getTime();
      if (typeof a.group === typeof b.group) {
        return a.weight > b.weight ? 1 : -1;
      }
      return c1Time > c2Time ? 1 : -1;
    });
    const funcVariables = functions.getFunctionVariableJson(func.variables);
    if (!this.isIntegrationTest()) {
      typeSystem = files.getTypesJson(func.fileId, typeSystem);
    }
    func.tests.forEach((test) => {
      const testCase = tests.getJson(test, this.i18n);
      const ReportTestCaseTransformer = classCreator(this.isIntegrationTest() && 'integrationTest');
      const transformer = new ReportTestCaseTransformer(
        testCase,
        funcVariables,
        typeSystem,
        this.i18n
      );
      try {
        transformer.transform(this.complete);
      } catch (error) {
        logger.error(`testCase transform error: fileId: ${func.fileId} function name: ${func.functionName} test case number: ${test.number}:`, error);
        return;
      }
      if (utility.isArrayType(transformer.input)) {
        transformer.input.forEach((input) => {
          // eslint-disable-next-line no-control-regex
          input.variable = input.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
        });
      }
      let annotations;
      test.udfs.forEach((udf) => {
        if (udf.name === '用例注释') {
          annotations = udf.value;
        }
      });
      // 集成测试调用关系
      const invokeLines = [];
      const invokedNodes = [];
      function traverse(node) {
        if (!node.functionId) return;
        if (invokedNodes.indexOf(node.functionId.toString()) > -1) return;
        if (node.children.size === 0) return;
        invokedNodes.push(node.functionId.toString());
        node.children.forEach((toFuncData) => {
          invokeLines.push({
            from: node.functionName,
            fromId: node.functionId.toString(),
            to: toFuncData.toFunc.functionName,
          });
          traverse(toFuncData.toFunc);
        });
      }
      if (this.isIntegrationTest()) {
        const funcRelations = func.relations.filter(
          (relation) => relation.testCases && relation.testCases.map((value) => String(value)).indexOf(String(testCase.testId)) > -1
        );
        const funcMap = this.arrangeRelations(funcRelations, func);
        if (funcMap.size > 0) {
          traverse(funcMap.get(func._id.toString()));
        }
        invokeLines.forEach((line) => {
          if (!this.functionAnnotationsMap.get(line.fromId)) {
            line.annotation = '';
          } else {
            line.annotation = this.functionAnnotationsMap.get(line.fromId);
          }
        });
      }
      // testMethod
      const testMethod = [];
      const codeToTestMethod = new Map(
        [
          [1, '等价类划分 Equivalence Partition'],
          [2, '边界值分析 Boundary Analysis'],
          [3, '错误猜测 Error Guess'],
          [4, '程序插桩 Program Insertion'],
          [5, '功能分解 Functional Decomposition'],
        ]
      );
      if (test.testMethod) {
        test.testMethod.forEach((code) => {
          testMethod.push(codeToTestMethod.get(code));
        });
      }
      if (testMethod.length === 0) {
        testMethod.push(codeToTestMethod.get(1));
      }
      resTests.push({
        testId: testCase.testId,
        index: test.number,
        description: testCase.description,
        identifier: testCase.identifier,
        requirementInfo: testCase.requirementInfo,
        typeNumber: testCase.typeNumber,
        resultKind: test.resultKindName,
        udfs: test.udfs,
        detail: {
          prelude: transformer.prelude,
          input: transformer.input,
          output: transformer.output,
          result: transformer.result,
        },
        isIntegrationTest: this.isIntegrationTest(),
        annotations,
        invokeLines,
        testMethod: testMethod.join(','),
      });
    });
    return resTests;
  }

  generateFileDocxReport(file, typeSystem, functions) {
    return {
      fileName: file.fileName,
      filePath: file.path,
      fileShortName: file.fileName.substring(0, file.fileName.lastIndexOf('.')),
      // summary需要处理回归版本的非新增非修改函数
      // summary: TonghaoReporter.getSummaryOfFile(functions),
      functions: this.getFunctionDetailsOfFile(typeSystem, functions),
      projectName: this.project.projectName,
      versionName: this.version.versionName,
      userName: this.creator.username,
      dirName: file.dirName,
    };
  }

  isRegressionTest() {
    return this.version.oldVersionId !== undefined;
  }

  static versionTypeSupport(vType) {
    return vType === versionType.unitTest || vType === versionType.integrationTest;
  }
}

/**
 * Export model definition object.
 */
module.exports = TonghaoReporter;
